from django.test import TestCase, TransactionTestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.core.files.uploadedfile import SimpleUploadedFile
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from unittest.mock import patch, MagicMock
from PIL import Image
import io
import json
from datetime import datetime, timedelta
from decimal import Decimal

from .models import SurveyRecord, SurveyImage, FavoriteLocation
from .serializers import SurveyRecordSerializer, FavoriteLocationSerializer

User = get_user_model()


class ModelTestCase(TestCase):
    """模型单元测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_survey_record_creation(self):
        """测试勘查记录创建"""
        survey = SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点',
            address='测试地址',
            latitude=Decimal('39.908823'),
            longitude=Decimal('116.397470'),
            orientation='坐北朝南',
            notes='测试备注',
            survey_date=datetime.now()
        )
        
        self.assertEqual(survey.user, self.user)
        self.assertEqual(survey.location_name, '测试地点')
        self.assertEqual(str(survey), f'测试地点 - {self.user.username}')
    
    def test_survey_record_without_coordinates(self):
        """测试无坐标勘查记录"""
        survey = SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点',
            address='测试地址',
            orientation='坐北朝南',
            notes='测试备注',
            survey_date=datetime.now()
        )
        
        self.assertIsNone(survey.latitude)
        self.assertIsNone(survey.longitude)
    
    def test_favorite_location_creation(self):
        """测试常用地点创建"""
        favorite = FavoriteLocation.objects.create(
            user=self.user,
            name='常用地点',
            address='常用地址',
            latitude=Decimal('39.908823'),
            longitude=Decimal('116.397470')
        )
        
        self.assertEqual(favorite.user, self.user)
        self.assertEqual(str(favorite), f'常用地点 - {self.user.username}')
    
    def test_favorite_location_unique_constraint(self):
        """测试常用地点唯一约束"""
        FavoriteLocation.objects.create(
            user=self.user,
            name='常用地点',
            address='常用地址'
        )
        
        # 尝试创建重复的常用地点
        with self.assertRaises(Exception):
            FavoriteLocation.objects.create(
                user=self.user,
                name='常用地点',
                address='其他地址'
            )


class SerializerTestCase(TestCase):
    """序列化器单元测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_survey_record_serializer_valid_data(self):
        """测试勘查记录序列化器有效数据"""
        data = {
            'location_name': '测试地点',
            'address': '测试地址',
            'latitude': '39.908823',
            'longitude': '116.397470',
            'orientation': '坐北朝南',
            'notes': '测试备注',
            'survey_date': '2024-01-01T10:00:00Z'
        }
        
        serializer = SurveyRecordSerializer(data=data)
        self.assertTrue(serializer.is_valid())
    
    def test_survey_record_serializer_without_coordinates(self):
        """测试无坐标序列化器验证"""
        data = {
            'location_name': '测试地点',
            'address': '测试地址',
            'orientation': '坐北朝南',
            'notes': '测试备注',
            'survey_date': '2024-01-01T10:00:00Z'
        }
        
        serializer = SurveyRecordSerializer(data=data)
        self.assertTrue(serializer.is_valid())
    
    def test_survey_record_serializer_missing_required_fields(self):
        """测试缺少必填字段的验证"""
        data = {
            'address': '测试地址',
        }
        
        serializer = SurveyRecordSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('location_name', serializer.errors)
        self.assertIn('survey_date', serializer.errors)
    
    def test_favorite_location_serializer(self):
        """测试常用地点序列化器"""
        data = {
            'name': '常用地点',
            'address': '常用地址',
            'latitude': '39.908823',
            'longitude': '116.397470'
        }
        
        serializer = FavoriteLocationSerializer(data=data)
        self.assertTrue(serializer.is_valid())


class APITestCase(APITestCase):
    """API接口测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        # 创建JWT token
        refresh = RefreshToken.for_user(self.user)
        self.access_token = str(refresh.access_token)
        
        # 设置认证头
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
        
        # 创建测试数据
        self.survey_data = {
            'location_name': '测试地点',
            'address': '测试地址',
            'latitude': '39.908823',
            'longitude': '116.397470',
            'orientation': '坐北朝南',
            'notes': '测试备注',
            'survey_date': '2024-01-01T10:00:00Z'
        }
    
    def test_create_survey_record(self):
        """测试创建勘查记录"""
        url = reverse('surveyrecord-list')
        response = self.client.post(url, self.survey_data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(SurveyRecord.objects.count(), 1)
        
        survey = SurveyRecord.objects.first()
        self.assertEqual(survey.user, self.user)
        self.assertEqual(survey.location_name, '测试地点')
    
    def test_list_survey_records(self):
        """测试获取勘查记录列表"""
        # 创建测试数据
        SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点1',
            address='测试地址1',
            survey_date=datetime.now()
        )
        SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点2',
            address='测试地址2',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 2)
    
    def test_survey_record_user_isolation(self):
        """测试用户数据隔离"""
        # 创建另一个用户
        other_user = User.objects.create_user(
            username='otheruser',
            email='other@example.com',
            password='otherpass123'
        )
        
        # 为另一个用户创建记录
        SurveyRecord.objects.create(
            user=other_user,
            location_name='其他用户地点',
            address='其他地址',
            survey_date=datetime.now()
        )
        
        # 为当前用户创建记录
        SurveyRecord.objects.create(
            user=self.user,
            location_name='当前用户地点',
            address='当前地址',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['location_name'], '当前用户地点')
    
    def test_update_survey_record(self):
        """测试更新勘查记录"""
        survey = SurveyRecord.objects.create(
            user=self.user,
            location_name='原地点',
            address='原地址',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-detail', kwargs={'pk': survey.id})
        update_data = {
            'location_name': '更新地点',
            'address': '更新地址',
            'survey_date': '2024-01-01T10:00:00Z'
        }
        
        response = self.client.patch(url, update_data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        survey.refresh_from_db()
        self.assertEqual(survey.location_name, '更新地点')
    
    def test_delete_survey_record(self):
        """测试删除勘查记录"""
        survey = SurveyRecord.objects.create(
            user=self.user,
            location_name='待删除地点',
            address='待删除地址',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-detail', kwargs={'pk': survey.id})
        response = self.client.delete(url)
        
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertEqual(SurveyRecord.objects.count(), 0)
    
    def test_search_survey_records(self):
        """测试搜索功能"""
        # 创建测试数据
        SurveyRecord.objects.create(
            user=self.user,
            location_name='北京公园',
            address='北京市朝阳区',
            notes='环境优美',
            survey_date=datetime.now()
        )
        SurveyRecord.objects.create(
            user=self.user,
            location_name='上海广场',
            address='上海市浦东区',
            notes='交通便利',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-search')
        
        # 测试按地点名称搜索
        response = self.client.get(url, {'location_name': '北京'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['location_name'], '北京公园')
        
        # 测试按备注搜索
        response = self.client.get(url, {'notes': '环境'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
    
    def test_favorite_location_crud(self):
        """测试常用地点CRUD操作"""
        favorite_data = {
            'name': '常用地点',
            'address': '常用地址',
            'latitude': '39.908823',
            'longitude': '116.397470'
        }
        
        # 创建
        url = reverse('favoritelocation-list')
        response = self.client.post(url, favorite_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # 列表
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        
        # 更新
        favorite_id = response.data['results'][0]['id']
        url = reverse('favoritelocation-detail', kwargs={'pk': favorite_id})
        update_data = {'name': '更新地点'}
        response = self.client.patch(url, update_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # 删除
        response = self.client.delete(url)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
    
    def test_unauthorized_access(self):
        """测试未授权访问"""
        # 清除认证头
        self.client.credentials()
        
        url = reverse('surveyrecord-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)


class ImageUploadTestCase(APITestCase):
    """图片上传测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        refresh = RefreshToken.for_user(self.user)
        self.access_token = str(refresh.access_token)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
        
        # 创建测试勘查记录
        self.survey = SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点',
            address='测试地址',
            survey_date=datetime.now()
        )
    
    def create_test_image(self):
        """创建测试图片文件"""
        image = Image.new('RGB', (100, 100), 'red')
        image_io = io.BytesIO()
        image.save(image_io, 'JPEG')
        image_io.seek(0)
        
        return SimpleUploadedFile(
            'test_image.jpg',
            image_io.getvalue(),
            content_type='image/jpeg'
        )
    
    def test_upload_image(self):
        """测试图片上传"""
        test_image = self.create_test_image()
        
        url = reverse('surveyrecord-upload-image', kwargs={'pk': self.survey.id})
        data = {
            'image': test_image,
            'orientation': '正门照片'
        }
        
        response = self.client.post(url, data, format='multipart')
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(SurveyImage.objects.count(), 1)
        
        image = SurveyImage.objects.first()
        self.assertEqual(image.survey_record, self.survey)
        self.assertEqual(image.orientation, '正门照片')
    
    def test_invalid_file_upload(self):
        """测试无效文件上传"""
        invalid_file = SimpleUploadedFile(
            'test.txt',
            b'not an image',
            content_type='text/plain'
        )
        
        url = reverse('surveyrecord-upload-image', kwargs={'pk': self.survey.id})
        data = {'image': invalid_file}
        
        response = self.client.post(url, data, format='multipart')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)


class CacheTestCase(TransactionTestCase):
    """缓存功能测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        refresh = RefreshToken.for_user(self.user)
        self.access_token = str(refresh.access_token)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
    
    @patch('django.core.cache.cache.get')
    @patch('django.core.cache.cache.set')
    def test_list_caching(self, mock_cache_set, mock_cache_get):
        """测试列表查询缓存"""
        mock_cache_get.return_value = None  # 模拟缓存未命中
        
        # 创建测试数据
        SurveyRecord.objects.create(
            user=self.user,
            location_name='测试地点',
            address='测试地址',
            survey_date=datetime.now()
        )
        
        url = reverse('surveyrecord-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # 验证缓存被调用
        mock_cache_get.assert_called()
        mock_cache_set.assert_called()


class PerformanceTestCase(APITestCase):
    """性能测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        refresh = RefreshToken.for_user(self.user)
        self.access_token = str(refresh.access_token)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
        
        # 创建大量测试数据
        self.create_test_data(100)
    
    def create_test_data(self, count):
        """创建测试数据"""
        surveys = []
        for i in range(count):
            surveys.append(SurveyRecord(
                user=self.user,
                location_name=f'测试地点{i}',
                address=f'测试地址{i}',
                survey_date=datetime.now() - timedelta(days=i)
            ))
        SurveyRecord.objects.bulk_create(surveys)
    
    def test_list_performance(self):
        """测试列表查询性能"""
        url = reverse('surveyrecord-list')
        
        import time
        start_time = time.time()
        response = self.client.get(url)
        end_time = time.time()
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # 响应时间应该在1秒内
        self.assertLess(end_time - start_time, 1.0)
    
    def test_search_performance(self):
        """测试搜索性能"""
        url = reverse('surveyrecord-search')
        
        import time
        start_time = time.time()
        response = self.client.get(url, {'search': '测试'})
        end_time = time.time()
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # 搜索响应时间应该在2秒内
        self.assertLess(end_time - start_time, 2.0)


class RateLimitTestCase(APITestCase):
    """限流测试"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        refresh = RefreshToken.for_user(self.user)
        self.access_token = str(refresh.access_token)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
    
    @patch('django.core.cache.cache.get')
    @patch('django.core.cache.cache.set')
    def test_rate_limiting(self, mock_cache_set, mock_cache_get):
        """测试API限流"""
        # 模拟已达到限流阈值
        mock_cache_get.return_value = 100  # 假设限制是100次/分钟
        
        url = reverse('surveyrecord-list')
        
        # 尝试发送请求，应该被限流
        # 注意：这里只是模拟测试，实际的限流逻辑可能需要更复杂的设置
        response = self.client.get(url)
        
        # 正常情况下应该返回200，如果被限流会返回429
        # 这里我们主要验证缓存调用
        mock_cache_get.assert_called()